//
// MessageBroker.cs
//
// Copyright (C) 2007 Fredrik Hedberg <fredrik@avafan.com>
// Copyright (C) 2008 Lukas Lipka <lukaslipka@gmail.com>
//

using System;
using System.IO;
using System.Threading;
using System.Collections.Generic;

using Dashboard;
using Dashboard.Util;

namespace Dashboard.Engine
{
	public class MessageBroker : IService
	{
		private ThreadPond thread_pond = null;
		private List<MessageHandlerMatcher> matchers = new List<MessageHandlerMatcher> ();

		private bool debug_messages = false;
		
		public void Initialize ()
		{
			if (Environment.GetEnvironmentVariable ("DASHBOARD_DEBUG_MESSAGES") != null)
				debug_messages = true;

			Engine.Log.Debug ("Starting message broker");

			thread_pond = new ThreadPond (5);
			thread_pond.Start ();
		}
		
		public void Dispose ()
		{ 
			matchers.Clear ();
			
			Engine.Log.Debug ("Stopping message broker");
			thread_pond.Stop ();
		}
		
		public void Send (Message message)
		{
			if (message == null) {
				Engine.Log.Warn ("Null message sent");
				return;
			}

			if (debug_messages) {
				Engine.Log.Debug ("Incoming Message\n{0}", message.ToString ());
			}

			// Queue the message handlers into the thread-pool
			// for execution.

			foreach (MessageHandler handler in MatchHandlers (message)) {
				MessageHandlerWorker worker = new MessageHandlerWorker (handler, message);
				thread_pond.Add (worker.Work);
			}
		}

		public void Send (ICollection<Message> messages)
		{
			foreach (Message message in messages)
				Send (message);
		}
		
		public void Subscribe (string pattern, MessageHandler handler)
		{
			Subscribe (pattern, null, handler);
		}

		public void Subscribe (string pattern, Type message_type, MessageHandler handler)
		{
			if ((String.IsNullOrEmpty (pattern) && message_type == null) || handler == null)
				return;

			MessageHandlerMatcher matcher = new MessageHandlerMatcher (pattern, message_type, handler);

			lock (matchers) {
				matchers.Add (matcher);
			}
		}

		public void Unsubscribe (MessageHandler handler)
		{
			Unsubscribe (null, handler);
		}

		public void Unsubscribe (string pattern, MessageHandler handler)
		{
			lock (matchers) {
				foreach (MessageHandlerMatcher matcher in matchers.ToArray ()) {
					if ((String.IsNullOrEmpty (pattern) || matcher.Pattern == pattern) && matcher.Handler == handler)
						matchers.Remove (matcher);
				}
			}
		}
		
		private List<MessageHandler> MatchHandlers (Message message)
		{
			List<MessageHandler> handlers = new List<MessageHandler> ();
			
			lock (matchers) {
				foreach (MessageHandlerMatcher matcher in matchers) {
					if (matcher.Match (message) && !handlers.Contains (matcher.Handler))
						handlers.Add (matcher.Handler);
				}
			}
			
			Engine.Log.Debug ("Matched {0} handlers for '{1}'", handlers.Count, message.Path);
			
			return handlers;
		}
		
		private class MessageHandlerMatcher 
		{
			private string pattern = null;
			private Type type = null;
			private MessageHandler handler = null;
			
			public MessageHandlerMatcher (string pattern, Type type, MessageHandler handler)
			{
				this.pattern = pattern;
				this.type = type;
				this.handler = handler;
			}
			
			public bool Match (Message message)
			{
				if (message == null)
					return false;

				// Match on exact type and subclasses of 
				// the required message type.

				Type actual_type = message.GetType ();

				if (type != null && type != actual_type && !actual_type.IsSubclassOf (type))
					return false;
				
				// Glob-match of the message path

				if (!String.IsNullOrEmpty (pattern) && !StringFu.GlobMatch (pattern, message.Path))
					return false;

				return true;
			}

			public string Pattern
			{
				get { return pattern; }
			}

			public MessageHandler Handler
			{
				get { return handler; }
			}
		}

		private class MessageHandlerWorker 
		{
			private MessageHandler handler = null;
			private Message message = null;

			public MessageHandlerWorker (MessageHandler handler, Message message)
			{
				this.handler = handler;
				this.message = message;
			}

			public void Work ()
			{
				if (handler == null || message == null)
					return;

				handler (message);
			}
		}
	}
}
